package middleware

import (
	"fmt"
	"time"

	"gitee.com/youkelike/ziam/zcode/zcode"
	"gitee.com/youkelike/ziam/zerrors"
	"gitee.com/youkelike/ziam/zutils/response"
	"gitee.com/youkelike/zlog"
	jwt "github.com/dgrijalva/jwt-go/v4"
	"github.com/gin-gonic/gin"
)

type Secret struct {
	Username string
	ID       string
	Key      string
	Expires  int64
}

func NewCacheAuth(get func(kid string) (Secret, error)) gin.HandlerFunc {
	return func(c *gin.Context) {
		header := c.Request.Header.Get("Authorization")
		if len(header) == 0 {
			response.Write(c, zerrors.WithCode(zcode.ErrMissingHeader, "Authorization header is empty."), nil)
			c.Abort()
			return
		}

		var rawJWT string
		// 这个用法挺妙
		fmt.Sscanf(header, "Bearer %s", &rawJWT)

		var secret Secret
		claims := jwt.MapClaims{}
		// 用解析 jwt 后得到的 token.Header["kid"]，去缓存中找 secret key，用它来验证 jwt 是否有效
		parsedT, err := jwt.ParseWithClaims(rawJWT, claims, func(token *jwt.Token) (interface{}, error) {
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, fmt.Errorf("unexpected signing method: %s", token.Header["alg"])
			}

			kid, ok := token.Header["kid"].(string)
			if !ok {
				return nil, fmt.Errorf("missing kid in token")
			}
			zlog.Infow("========", zlog.String("kid", kid))

			var err error
			secret, err = get(kid)
			if err != nil {
				return nil, fmt.Errorf("missing secret")
			}
			zlog.Infow("========secret:", zlog.Any("secret", secret))

			return []byte(secret.Key), nil
		}, jwt.WithAudience(AuthzAudience))
		if err != nil || !parsedT.Valid {
			response.Write(c, zerrors.WithCode(zcode.ErrSignatureInvalid, err.Error()), nil)
			c.Abort()
			return
		}

		if KeyExpired(secret.Expires) {
			tm := time.Unix(secret.Expires, 0).Format("2006-01-02 15:04:05")
			response.Write(c, zerrors.WithCode(zcode.ErrExpired, "expired at: %s", tm), nil)
			c.Abort()
			return
		}
		zlog.Infow("===========set username", zlog.String("username", secret.Username))
		c.Set(UsernameKey, secret.Username)
		c.Next()
	}
}

func KeyExpired(expires int64) bool {
	if expires > 1 {
		return time.Now().After(time.Unix(expires, 0))
	}
	return false
}
